#include <QCoreApplication>
#include <QTimer>
#include <QSerialPort>
#include <QThread>
#include <QDateTime>
#include <QDebug>

#include "setting/JSON_settings.h"
#include "coordinator/coordinator.h"
#include "papercontrol/papercontrol.h"
#include "notecounter/notecounter_controller.h"

#define NC_CMD_CHECK_TIME		100			// 100ms
#define NC_RESPONSE_WAIT_TIME	10			// (10 * 100) = 2 seconds
#define NC_IDLE_TIME			30			// (30 * 100) = 3 seconds

QStringList NOTE_LIST = {
	"CNY100",
	" CNY50",
	" CNY20",
	" CNY10",
	"  CNY5",
	"  CNY1",
	" CNY05",
	" CNY01",
	"",
	""
};

NCController_Worker::NCController_Worker(QObject *parent)
	: QObject(parent)
{
	m_port = nullptr;
	m_worker_timer = nullptr;
	m_transaction_mode = MODE_INIT;
	m_reject_empty = true;
	m_collect_empty = true;
	m_entry_empty = true;
	m_shell_closed = false;

    m_online = false;
}

NCController_Worker::~NCController_Worker()
{
	stop_work();
}

void NCController_Worker::start_work()
{
	m_port = new QSerialPort(m_param.port, this);
	if (m_port) {
		m_port->setBaudRate(m_param.baud);
        m_port->setDataBits(m_param.data_bits);
		m_port->setStopBits(m_param.stop_bits);
		m_port->setParity(m_param.parity);
		if (!m_port->open(QIODevice::ReadWrite)) {
            qInfo() << "[NOTECOUNTER]Open Serial Port " << m_param.port << " Failed!";
            delete m_port;
            m_port = nullptr;
            QTimer::singleShot(10000, this, &NCController_Worker::start_work);
			return;
		}
	}

    qInfo() << "[NOTECOUNTER]Connecting Note Counter Module at " << m_param.port;
    qInfo() << "[NOTECOUNTER]    Baud: " << m_param.baud;
    qInfo() << "[NOTECOUNTER]DataBits: " << m_param.data_bits;
    qInfo() << "[NOTECOUNTER]  Parity: " << m_param.parity;
    qInfo() << "[NOTECOUNTER]StopBits: " << m_param.stop_bits;

    m_response.clear();
    m_current_cmd.clear();
    m_wait_time = NC_RESPONSE_WAIT_TIME;
	m_worker_timer = new QTimer(this);

    connect(m_worker_timer, &QTimer::timeout, this, &NCController_Worker::process_cmd_resp);
    m_worker_timer->start(NC_CMD_CHECK_TIME);

    m_shell_cmd_status = SHELL_CMD_NONE;
	transfer_cmd(pack_command(CNTR_EXIT_COIN_MODE_CMD));
	transfer_cmd(pack_command(CNTR_EXIT_WORK_MODE_CMD));
    set_mode(MODE_INIT);
}

void NCController_Worker::stop_work()
{
	if (m_worker_timer)
		delete m_worker_timer;
	if (m_port)
		delete m_port;
	m_worker_timer = nullptr;
	m_port = nullptr;
}

void NCController_Worker::setSerialParam(SerialConfig param)
{
	NCController_Worker::m_param = param;
}

void NCController_Worker::process_cmd_resp()
{
	if (m_port) {
        m_wait_time++;
        if (m_current_cmd.isEmpty() && m_cmd_list.length()) {
            // If the time-gap between two command is too short(< 200ms?), the Note Counter may
            // fail to issue response
            // So, we delay the issue of next command to the next timeout procedure
            if (m_wait_time >= 6) {
	            m_current_cmd = m_cmd_list.takeFirst();
	            cmd_status(m_current_cmd[5]);
				m_port->write(m_current_cmd);
				m_current_cmd_fail_cnt = 0;
				m_wait_time = 0;
            }
		}
		m_response += m_port->readAll();
        QByteArray msg_ba = rcsv_analyze(m_response);
		if (msg_ba.length()) {
			m_online = true;
            parse_msg(msg_ba);
			// response received, send command at next time
			m_wait_time = 2;
            m_current_cmd.clear();
		}
		else if (m_wait_time >= NC_RESPONSE_WAIT_TIME && !m_current_cmd.isEmpty()) {
			m_online = false;
			qInfo() << "[NOTECOUNTER]No response for command" << m_current_cmd.toHex(' ');
			m_current_cmd_fail_cnt++;
			if (m_current_cmd_fail_cnt < 3) {
                qInfo() << "[NOTECOUNTER]Re-issue command" << m_current_cmd.toHex(' ');
				m_port->write(m_current_cmd);
				m_wait_time = 0;
			}
			else
				m_current_cmd.clear();
		}
	}
}

void NCController_Worker::transfer_cmd(QByteArray cmd)
{
	m_cmd_list.append(cmd);
}

QByteArray NCController_Worker::rcsv_analyze(QByteArray &raw)
{
	QByteArray act_resp, resp_body;

    unsigned char stx1[] = {
    	0xa1, 0xa2, 0xa3, 0xa4,			// STX(4 bytes)
    	0x00
    };
    unsigned char stx2[] = {
    	0xb1, 0xb2, 0xb3, 0xb4,			// STX(4 bytes)
    	0x00
    };

	while (raw.length() >= 9) {
		if (!raw.startsWith((char *) stx1) && !raw.startsWith((char *) stx2)) {
			raw.remove(0, 1);
			continue;
		}
        uint16_t resp_length;
		if (raw.startsWith((char *) stx1)) {
			// Response with STX A1 A2 A3 A4
			// 1 bytes packet size
			resp_length = (uint8_t) raw[4];
			if (resp_length + 5 > raw.length())
				break;
			// Response packet
			act_resp = raw.mid(0, resp_length + 5);
			raw.remove(0, resp_length + 5);
			// Reponse body
		    resp_body = act_resp.mid(5, act_resp.length() - 8);

			// check the checksum of response
		    unsigned char cksum = 0;
		    for (int idx = 0; idx < act_resp.length() - 1; idx++)
		        cksum ^= (uint8_t) act_resp[idx];
		    if (cksum != (uint8_t) act_resp[act_resp.length() - 1]) {
		    	qDebug() << "Response Checksum Error" << act_resp.toHex(' ');
		    	continue;
	    	}
		}
		else {
			// Response with STX B1 B2 B3 B4
			// 2 bytes packet size
			resp_length = (uint8_t) raw[4] + ((uint8_t) raw[5] << 8);
			if (resp_length + 6 > raw.length())
				break;
			// Response packet
			act_resp = raw.mid(0, resp_length + 6);
			raw.remove(0, resp_length + 6);
			// Reponse body
		    resp_body = act_resp.mid(6, act_resp.length() - 9);

			// check the checksum of response
		    unsigned char cksum = 0;
		    for (int idx = 0; idx < act_resp.length() - 1; idx++)
		        cksum ^= (uint8_t) act_resp[idx];
		    cksum = ((cksum & 0x0f) << 4) + ((cksum & 0xf0) >> 4);
		    uint8_t act_cksum = (uint8_t) act_resp[act_resp.length() - 1];
		    if (cksum != act_cksum) {
//		    	qDebug() << "Response Checksum Error" << QString("%1 %2").arg(cksum, 2, 16, QLatin1Char('0')).arg(act_cksum, 2, 16, QLatin1Char('0'));
//		    	continue;
		    }
		}

		return resp_body;
	}

    return QByteArray();
}

void NCController_Worker::parse_msg(QByteArray msg)
{
	uint8_t msg_code;
	QByteArray msg_data;
	uint16_t cnt;
	NoteInfo note_info;
	uint16_t error1, error2;
    uint8_t sys_status;

    cnt = 0;
	msg_code = msg[0];
	msg_data = msg.mid(1);
    switch (msg_code) {
    case CNTR_COMM_TEST_CMD:
    case CNTR_FAULT_CLEAR_CMD:
	case CNTR_PAUSE_COUNT_CMD:
		response_status(msg_code, 0);
    	break;
    case CNTR_EXIT_WORK_MODE_CMD:
    case CNTR_ENTER_WORK_MODE_CMD:
    case CNTR_START_COUNT_CMD:
    case CNTR_CLEAR_TRANSACTION_CMD:
    case CNTR_OPEN_SHELL_CMD:
    case CNTR_CLOSE_SHELL_CMD:
    case CNTR_ENTER_COIN_MODE_CMD:
    case CNTR_EXIT_COIN_MODE_CMD:
    case CNTR_MODE_SELECT_CMD:
        if (msg_data.length() != 1) {
    		qDebug() << "[NOTECOUNTER]Response Data Error";
    		break;
        }
        response_status(msg_code, (uint8_t) msg_data[0]);
        m_cmd_status = (uint8_t) msg_data[0];
        emit nc_status(m_cmd_status | (msg_code << 8));
    	break;
    case CNTR_STATUS_CMD:
        if (msg_data.length() != 3) {
    		qDebug() << "[NOTECOUNTER]Response Data Error";
    		break;
        }
        m_shell_closed = ((uint8_t) msg_data[2] == 0x02);
        m_entry_empty = ((uint8_t) msg_data[1] == 0x02);
        m_reject_empty = (~((uint8_t) msg_data[0]) & 0x02);
        m_collect_empty = (~((uint8_t) msg_data[0]) & 0x01);
    	break;
    case CNTR_COUNT_INFO_RESPONSE1:
    case CNTR_COUNT_INFO_RESPONSE2:
    	qInfo() << "[NOTECOUNTER]Count Info";
    	if (msg_data.length() != 44) {
    		qDebug() << "[NOTECOUNTER]\tResponse Data Error";
    		break;
    	}
    	qInfo() << "[NOTECOUNTER]\tCounter Status: " << QString("%1").arg((uint16_t) msg_data[0], 2, 16, QLatin1Char('0'));
        m_cmd_status = (uint8_t) msg_data[0];
    	if ((uint16_t) msg_data[1] == 0x01) {
    		qInfo() << "[NOTECOUNTER]\tRMB";
    		note_info.total_count = 0;
	    	for (int k = 0; k < NOTE_TYPE_COUNT; k++) {
	    		cnt = (uint16_t) msg_data[2 + 2 * k];
	    		cnt += ((uint16_t) msg_data[2 + 2 * k + 1]) << 8;
	    		if (cnt)
	    			qInfo() << "[NOTECOUNTER]\t   Current" << NOTE_LIST[k] << " " << cnt;
	    		note_info.note_count[k] = cnt;
	    		note_info.total_count += cnt;
	    	}
	    	for (int k = 0; k < NOTE_TYPE_COUNT; k++) {
	    		cnt = (uint16_t) msg_data[2 + 2 * NOTE_TYPE_COUNT + 2 * k];
	    		cnt += ((uint16_t) msg_data[2 + 2 * NOTE_TYPE_COUNT + 2 * k + 1]) << 8;
	    		if (cnt)
	    			qInfo() << "[NOTECOUNTER]\tTransaction" << NOTE_LIST[k] << " " << cnt;
	    	}
	    	note_info.rejected_count = (uint16_t) msg_data[2 + 2 * NOTE_TYPE_COUNT + 2 * NOTE_TYPE_COUNT];
	    	if (transaction_mode() == MODE_COUNT && note_info.rejected_count) {
	    		qInfo() << "[NOTECOUNTER]\tRejected Note: " << QString("%1").arg(note_info.rejected_count);
	    		note_info.total_count += note_info.rejected_count;
	    	}
	    	if (cnt)
	    		qInfo() << "[NOTECOUNTER]\tRejected Note: " << QString("%1").arg(note_info.rejected_count);
            sys_status = (uint16_t) msg_data[2 + 2 * NOTE_TYPE_COUNT + 2 * NOTE_TYPE_COUNT + 1];
            qInfo() << "[NOTECOUNTER]\tSystem Status:" << QString("%1").arg(sys_status);
			emit transaction_count_info(note_info);
    	}
    	else
    		qInfo() << "[NOTECOUNTER]\tUnknown Note";
    	break;
    case CNTR_NOTESERIAL_REPORT_RESPONSE:
	    if (transaction_mode() != MODE_COUNT) {
	    	qInfo() << "[NOTECOUNTER]Note Serial Info";
	    	cnt = (uint16_t) msg_data[0];
	    	cnt += ((uint16_t) msg_data[1]) << 8;
	    	if (msg_data.length() != cnt * 25 + 2) {
	    		qInfo() << "[NOTECOUNTER]\tResponse Data Error(Serial_Info)" << msg_data.length() << "(Note Count:" << cnt * 25 + 2 << ")";
	    		break;
	    	}
	    	qInfo() << "[NOTECOUNTER]\tNote Count: " << cnt;
	    	for (int k = 0; k < cnt; k++)
	    		qInfo() << "[NOTECOUNTER]\t" << msg_data.mid(2 + k * 25, 25);
	    }
    	m_collect_empty = false;
		emit count_complete(m_cmd_status);
    	break;
    case CNTR_STORE_IN_PROGRESS_RESPONSE:
    	qInfo() << "[NOTECOUNTER]Store in Progress";
        if (msg_data.length() != 1) {
    		qInfo() << "[NOTECOUNTER]\tResponse Data Error(Store in Progress)" << msg_data.length() << "(Note Count:" << cnt << ")";
    		break;
        }
        qInfo() << "[NOTECOUNTER]\tResponse Status: " << QString("%1").arg((uint16_t) msg_data[0], 2, 16, QLatin1Char('0'));
        m_cmd_status = (uint8_t) msg_data[0];
        response_status(msg_code, (uint8_t) msg_data[0]);
		emit store_in_progress();
        emit nc_status(m_cmd_status | (msg_code << 8));
    	break;
    case CNTR_COMPLETE_STORE_RESPONSE:
    	qInfo() << "[NOTECOUNTER]Store Complete";
    	if (msg_data.length() != 43) {
    		qInfo() << "[NOTECOUNTER]\tResponse Data Error(Store Complete)" << msg_data.length() << "(Note Count:" << cnt << ")";
    		break;
    	}
    	qInfo() << "[NOTECOUNTER]\tCounter Status: " << QString("%1").arg((uint16_t) msg_data[0], 2, 16, QLatin1Char('0'));
        m_cmd_status = (uint8_t) msg_data[0];
		emit store_complete(m_cmd_status);
    	if ((uint16_t) msg_data[1] == 0x01) {
    		qInfo() << "[NOTECOUNTER]\tRMB";
	    	for (int k = 0; k < NOTE_TYPE_COUNT; k++) {
	    		cnt = (uint16_t) msg_data[2 + 2 * k];
	    		cnt += ((uint16_t) msg_data[2 + 2 * k + 1]) << 8;
	    		if (cnt)
	    			qInfo() << "\t" << NOTE_LIST[k] << " " << cnt;
	    	}
	    	for (int k = 0; k < NOTE_TYPE_COUNT; k++) {
	    		cnt = (uint16_t) msg_data[2 + 2 * NOTE_TYPE_COUNT + 2 * k];
	    		cnt += ((uint16_t) msg_data[2 + 2 * NOTE_TYPE_COUNT + 2 * k + 1]) << 8;
	    		if (cnt)
	    			qInfo() << "[NOTECOUNTER]\t" << NOTE_LIST[k] << " " << cnt;
	    	}
	    	if ((uint16_t) msg_data[2 + 2 * NOTE_TYPE_COUNT + 2 * NOTE_TYPE_COUNT])
	    		qInfo() << "[NOTECOUNTER]\tRejected Note: " << QString("%1").arg((uint16_t) msg_data[2 + 2 * NOTE_TYPE_COUNT + 2 * NOTE_TYPE_COUNT]);
    	}
    	else
    		qInfo() << "[NOTECOUNTER]\tUnknown Note";
        response_status(msg_code, m_cmd_status);
        emit nc_status(m_cmd_status | (msg_code << 8));
    	break;
	case CNTR_FAULT_REPORT_CMD_RESPONSE:
        if (msg_data.length() != 4) {
    		qInfo() << "[NOTECOUNTER]\tResponse Data Error(Fault Report)" << msg_data.length();
    		break;
        }
        error1 = (uint8_t) msg_data[0]; error1 += ((uint8_t) msg_data[1]) << 8;
        error2 = (uint8_t) msg_data[2]; error2 += ((uint8_t) msg_data[3]) << 8;
    	qInfo() << "[NOTECOUNTER]Fault Error" << QString("%1 %2").arg(error1, 4, 16, QLatin1Char('0')).arg(error2, 4, 16, QLatin1Char('0'));
    	emit nc_error(error1, error2);
		break;
	default:
		qInfo() << "[NOTECOUNTER]Unknown Response" << msg.toHex(' ');
		break;
    }
}

void NCController_Worker::check_online()
{
	transfer_cmd(pack_command(CNTR_COMM_TEST_CMD));
}

void NCController_Worker::clear_transaction()
{
	transfer_cmd(pack_command(CNTR_CLEAR_TRANSACTION_CMD));
}

/*
void NCController_Worker::clear_faults()
{
	transfer_cmd(pack_command(CNTR_FAULT_CLEAR_CMD));
}

void NCController_Worker::start_transaction()
{
	clear_transaction();
	transfer_cmd(pack_command(CNTR_ENTER_WORK_MODE_CMD));
    transfer_cmd(pack_command(CNTR_START_COUNT_CMD));
}


void NCController_Worker::complete_transaction()
{
	transfer_cmd(pack_command(CNTR_EXIT_WORK_MODE_CMD));
}
*/

void NCController_Worker::start_store()
{
	transfer_cmd(pack_command(CNTR_STORE_NOTE_CMD));

    QTimer::singleShot(4000,
    					this,
    					[this] {
    						emit paper_action(PaperControl_Worker::PAPER_POS_2);
    					});
}

void NCController_Worker::start_count()
{
	qInfo() << "[NOTECOUNTER]Start Count";
	transfer_cmd(pack_command(CNTR_START_COUNT_CMD));
}

void NCController_Worker::complete_store()
{
	transfer_cmd(pack_command(CNTR_COMPLETE_STORE_NOTE_CMD));
}

void NCController_Worker::open_shell()
{
	if (!m_shell_closed)
		return;
	qInfo() << "[NOTECOUNTER]Open Shell";

	transfer_cmd(pack_command(CNTR_OPEN_SHELL_CMD));
	transfer_cmd(pack_command(CNTR_STATUS_CMD));
	transfer_cmd(pack_command(CNTR_STATUS_CMD));

    QTimer::singleShot(2500,
    					this,
    					[this] {
    						emit paper_action(PaperControl_Worker::PAPER_POS_0);
    					});
}

void NCController_Worker::close_shell()
{
	if (m_shell_closed)
		return;
	emit paper_action(PaperControl_Worker::PAPER_POS_1);

    QTimer::singleShot(1500,
    					this,
    					[this] {
							qInfo() << "[NOTECOUNTER]Close Shell";
							transfer_cmd(pack_command(CNTR_CLOSE_SHELL_CMD));
							transfer_cmd(pack_command(CNTR_STATUS_CMD));
							transfer_cmd(pack_command(CNTR_STATUS_CMD));
    					});
}

void NCController_Worker::start_coin_transaction()
{
	close_shell();
}

void NCController_Worker::complete_coin_transaction()
{
//	open_shell();
}

void NCController_Worker::inquiry_nc_status()
{
	transfer_cmd(pack_command(CNTR_STATUS_CMD));
}

void NCController_Worker::set_mode(uint32_t mode)
{
	switch (m_transaction_mode) {
	case MODE_INIT:
		break;
	case MODE_INTELLIGENT:
	case MODE_COUNT:
        transfer_cmd(pack_command(CNTR_MODE_SELECT_CMD, MODE_MIXED));
	case MODE_MIXED:
	case MODE_NOTE_TRANSACTION:
		if (m_shell_closed && !m_collect_empty)
			open_shell();
		break;
	case MODE_COIN_TRANSACTION:
		transfer_cmd(pack_command(CNTR_EXIT_COIN_MODE_CMD));
		transfer_cmd(pack_command(CNTR_ENTER_WORK_MODE_CMD));
		break;
	}
	m_transaction_mode = MODE_INIT;

	switch (mode) {
	case MODE_INIT:
		transfer_cmd(pack_command(CNTR_ENTER_WORK_MODE_CMD));
		transfer_cmd(pack_command(CNTR_STATUS_CMD));
		transfer_cmd(pack_command(CNTR_STATUS_CMD));
		break;
	case MODE_INTELLIGENT:
	case MODE_MIXED:
	case MODE_COUNT:
        transfer_cmd(pack_command(CNTR_MODE_SELECT_CMD, mode));
    	start_count();
    	m_transaction_mode = mode;
		break;
	case MODE_NOTE_TRANSACTION:
		transfer_cmd(pack_command(CNTR_ENTER_WORK_MODE_CMD));
     	start_count();
		m_transaction_mode = MODE_NOTE_TRANSACTION;
		break;
	case MODE_COIN_TRANSACTION:
		transfer_cmd(pack_command(CNTR_EXIT_WORK_MODE_CMD));
    	transfer_cmd(pack_command(CNTR_ENTER_COIN_MODE_CMD));
		open_shell();
		m_transaction_mode = MODE_COIN_TRANSACTION;
		break;
	}
}

QByteArray NCController_Worker::pack_command(uint32_t cmd, uint32_t para)
{
	QByteArray ba;
                                    
    unsigned char cmd1[] = {
    	0xa1, 0xa2, 0xa3, 0xa4,			// STX(4 bytes)
    	0x04,							// Size(1 bytes)
    	0x32,							// Cmd(1 Byte)
    	0xbb, 0xbb,						// ETX(2 Bytes)
    	0x00							// BCC(1 Byte)
    };
    unsigned char cmd2[] = {
    	0xa1, 0xa2, 0xa3, 0xa4,			// STX(4 bytes)
    	0x0a,							// Size(1 bytes)
    	0x22,							// Cmd(1 Byte)
    	0xff, 0xff,						// Data(2 Bytes)
    	0xff, 0xff, 0xff, 0xff,			// Data(4 Bytes)
    	0xbb, 0xbb,						// ETX(2 Bytes)
    	0x00							// BCC(1 Byte)
    };
    unsigned char cmd3[] = {
    	0xa1, 0xa2, 0xa3, 0xa4,			// STX(4 bytes)
    	0x12,							// Size(1 bytes)
    	0x21,							// Cmd(1 Byte)
    	0x32,0x30,0x31,0x38,0x30,0x34,0x32,0x36,0x31,0x30,0x31,0x31,0x31,0x35,			// Data(14 Bytes)
    	0xbb, 0xbb,						// ETX(2 Bytes)
    	0x00							// BCC(1 Byte)
    };
    unsigned char cmd4[] = {
    	0xa1, 0xa2, 0xa3, 0xa4,			// STX(4 bytes)
    	0x05,							// Size(1 bytes)
    	0x02,							// Cmd(1 Byte)
    	0x00,							// Data(1 Byte)
    	0xbb, 0xbb,						// ETX(2 Bytes)
    	0x00							// BCC(1 Byte)
    };

	switch (cmd) {
	case CNTR_STATUS_CMD:
	case CNTR_COMM_TEST_CMD:
	case CNTR_FAULT_CLEAR_CMD:
	case CNTR_EXIT_WORK_MODE_CMD:
	case CNTR_PAUSE_COUNT_CMD:
	case CNTR_STORE_NOTE_CMD:
    case CNTR_COMPLETE_STORE_NOTE_CMD:
	case CNTR_CLEAR_TRANSACTION_CMD:
	case CNTR_OPEN_SHELL_CMD:
	case CNTR_CLOSE_SHELL_CMD:
	case CNTR_ENTER_COIN_MODE_CMD:
	case CNTR_EXIT_COIN_MODE_CMD:
	case CNTR_NOTE_INFO_CMD:
		cmd1[5] = cmd;
		ba = QByteArray((char *) cmd1, 9);
		break;

	case CNTR_START_COUNT_CMD:
		ba = QByteArray((char *) cmd2, 15);
		break;
	case CNTR_ENTER_WORK_MODE_CMD:
		ba = QByteArray((char *) cmd3, 23);
        ba.replace(6, 14, QDateTime::currentDateTime().toString("yyyyMMddhhmmss").toLatin1());
		break;

    case CNTR_MODE_SELECT_CMD:
		ba = QByteArray((char *) cmd4, 10);
        ba[6] = para & 0xff;
		break;
	}

	// calculate the checksum of command package
    unsigned char cksum = 0;
    for (int idx = 0; idx < ba.length() - 1; idx++)
        cksum ^= ba[idx];
    ba[ba.length() - 1] = cksum;

	return ba;
}

void NCController_Worker::cmd_status(uint8_t cmd)
{
	QString cmd_name;

	switch (cmd) {
    case CNTR_MODE_SELECT_CMD:
    	cmd_name = "MODE_SELECT";
    	break;
    case CNTR_STATUS_CMD:
    	cmd_name = "STATUS_INQUIRY";
    	break;
    case CNTR_COMM_TEST_CMD:
    	cmd_name = "TEST_COMM";
    	break;
    case CNTR_FAULT_CLEAR_CMD:
    	cmd_name = "CLEAR_FAULT";
    	break;
	case CNTR_PAUSE_COUNT_CMD:
    	cmd_name = "CLEAR_FAULT";
    	break;
    case CNTR_EXIT_WORK_MODE_CMD:
    	cmd_name = "EXIT_WORK_MODE";
    	break;
    case CNTR_ENTER_WORK_MODE_CMD:
    	cmd_name = "ENTER_WORKMODE";
    	break;
    case CNTR_START_COUNT_CMD:
    	cmd_name = "START_COUNT";
    	break;
    case CNTR_CLEAR_TRANSACTION_CMD:
    	cmd_name = "CLEAR_TRANS";
    	break;
    case CNTR_OPEN_SHELL_CMD:
    	cmd_name = "OPEN_SHELL";
    	break;
    case CNTR_CLOSE_SHELL_CMD:
    	cmd_name = "CLOSE_SHELL";
    	break;
    case CNTR_ENTER_COIN_MODE_CMD:
    	cmd_name = "ENTER_COIN_MODE";
    	break;
    case CNTR_EXIT_COIN_MODE_CMD:
    	cmd_name = "EXIT_COIN_MODE";
    	break;
    case CNTR_COUNT_INFO_RESPONSE1:
    	cmd_name = "COUNT_INFO1";
    	break;
    case CNTR_COUNT_INFO_RESPONSE2:
    	cmd_name = "COUNT_INFO2";
    	break;
    case CNTR_ONLINE_CHECK_RESPONSE:
    	cmd_name = "ONLINE_CHECK";
    	break;
    case CNTR_NOTESERIAL_REPORT_RESPONSE:
    	cmd_name = "SERIAL_REPORT";
    	break;
    case CNTR_STORE_NOTE_CMD:
    	cmd_name = "STORE_START";
    	break;
    case CNTR_COMPLETE_STORE_NOTE_CMD:
    	cmd_name = "STORE_COMPLETE";
    	break;
	case CNTR_FAULT_REPORT_CMD_RESPONSE:
    	cmd_name = "FAULT_REPORT";
    	break;
	default:
		cmd_name = QString("UNKNOWN_CMD %1").arg(cmd, 2, 16, QLatin1Char('0'));
		break;
	}

	// suppress the log for large status commands
	if (cmd != CNTR_STATUS_CMD)
		qInfo().noquote() << QString("[NOTECOUNTER]Issue Command: %1").arg(cmd_name, 19);
}

void NCController_Worker::response_status(uint8_t cmd, uint8_t status)
{
	QString cmd_name, status_name;

	status_to_text(cmd, status, cmd_name, status_name);
	qInfo().noquote() << QString("[NOTECOUNTER]Response from %1: %2 %3").arg(cmd_name, 20).arg(status, 2, 16, QLatin1Char('0')).arg(status_name);
}

void NCController_Worker::status_to_text(uint8_t cmd, uint8_t status, QString &cmd_name, QString &status_name)
{
	switch (cmd) {
    case CNTR_MODE_SELECT_CMD:
    	cmd_name = "MODE_SELECT";
    	switch (status) {
    	case MODE_INTELLIGENT:
    		status_name = tr("Intelligent Mode");
    		break;
    	case MODE_MIXED:
    		status_name = tr("Mixed Mode");
    		break;
    	case MODE_COUNT:
    		status_name = tr("Count Mode");
    		break;
    	default:
    		status_name = tr("Unknown Mode");
    		break;
    	}
    	break;
    case CNTR_COMM_TEST_CMD:
    	cmd_name = "TEST_COMM";
    	break;
    case CNTR_FAULT_CLEAR_CMD:
    	cmd_name = "CLEAR_FAULT";
    	break;
	case CNTR_PAUSE_COUNT_CMD:
    	cmd_name = "PAUSE_COUNT";
    	break;
    case CNTR_EXIT_WORK_MODE_CMD:
    	cmd_name = "EXIT_WORK_MODE";
    	switch (status) {
    	case CNTR_STATUS_SUCCEED:
    		status_name = tr("Success");
    		break;
    	case CNTR_STATUS_COLLECT_NOT_EMPTY:
    		status_name = tr("Notes in Collection");
    		emit count_exception(cmd, MSG_nc_collect_not_empty, status_name);
    		break;
    	case CNTR_STATUS_REJECT_NOT_EMPTY:
    		status_name = tr("Notes in Reject");
    		emit count_exception(cmd, MSG_nc_reject_not_empty, status_name);
    		break;
    	case CNTR_STATUS_ENTRY_NOT_EMPTY:
    		status_name = tr("Notes in Entry");
    		emit count_exception(cmd, MSG_nc_entry_not_empty, status_name);
    		break;
    	default:
    		status_name = tr("Unknown Error");
    		break;
    	}
    	break;
    case CNTR_ENTER_WORK_MODE_CMD:
    	cmd_name = "ENTER_WORKMODE";
    	switch (status) {
    	case CNTR_STATUS_SUCCEED:
    		status_name = tr("Success");
    		break;
    	default:
    		status_name = tr("Unknown Error");
    		break;
    	}
    	break;
    case CNTR_START_COUNT_CMD:
    	cmd_name = "START_COUNT";
    	switch (status) {
    	case CNTR_STATUS_SUCCEED:
    		status_name = tr("Success");
    		emit count_started();
    		break;
    	case CNTR_STATUS_ENTRY_NO_NOTE:
    		status_name = tr("No Notes in Entry");
    		emit count_exception(cmd, MSG_nc_entry_empty, status_name);
    		break;
    	case CNTR_STATUS_REJECT_NOT_EMPTY:
    		status_name = tr("Notes in Reject");
    		emit count_exception(cmd, MSG_nc_reject_not_empty, status_name);
    		break;
    	default:
//    		status_name = tr("Unknown Error");
//    		emit count_exception(cmd, MSG_other_errors, status_name);
    		break;
    	}
    	break;
    case CNTR_CLEAR_TRANSACTION_CMD:
    	cmd_name = "CLEAR_TRANS";
    	switch (status) {
    	case CNTR_STATUS_SUCCEED:
    		status_name = tr("Success");
    		break;
    	case CNTR_STATUS_IN_WORKING:
    		status_name = tr("Counter in working");
    		break;
    	case CNTR_STATUS_NOTE_NOT_CLEAR:
    		status_name = tr("Note not cleared");
    		break;
    	default:
    		status_name = tr("Unknown Error");
    		break;
    	}
    	break;
    case CNTR_OPEN_SHELL_CMD:
    	cmd_name = "OPEN_SHELL";
    	switch (status) {
    	case CNTR_STATUS_SUCCEED:
    		status_name = tr("Success");
    		break;
    	case CNTR_STATUS_REJECT_NOT_EMPTY:
    		status_name = tr("Notes in Reject");
    		break;
    	default:
    		status_name = tr("Unknown Error");
    		break;
    	}
    	break;
    case CNTR_CLOSE_SHELL_CMD:
    	cmd_name = "CLOSE_SHELL";
    	switch (status) {
    	case CNTR_STATUS_SUCCEED:
    		status_name = tr("Success");
    		break;
    	case CNTR_STATUS_COLLECT_NOT_EMPTY1:
    		status_name = tr("Notes in Collection");
    		break;
    	default:
    		status_name = tr("Unknown Error");
    		break;
    	}
    	break;
    case CNTR_ENTER_COIN_MODE_CMD:
    	cmd_name = "ENTER_COIN_MODE";
    	switch (status) {
    	case CNTR_STATUS_SUCCEED:
    		status_name = tr("Success");
    		break;
    	default:
    		status_name = tr("Unknown Error");
    		break;
    	}
    	break;
    case CNTR_EXIT_COIN_MODE_CMD:
    	cmd_name = "EXIT_COIN_MODE";
    	switch (status) {
    	case CNTR_STATUS_SUCCEED:
    		status_name = tr("Success");
    		break;
    	case CNTR_STATUS_COLLECT_NOT_EMPTY:
    		status_name = tr("Notes in Collection");
    		break;
    	case CNTR_STATUS_REJECT_NOT_EMPTY:
    		status_name = tr("Notes in Reject");
    		break;
    	case CNTR_STATUS_ENTRY_NOT_EMPTY:
    		status_name = tr("Notes in Entry");
    		break;
    	default:
    		status_name = tr("Unknown Error");
    		break;
    	}
    	break;
    case CNTR_COUNT_INFO_RESPONSE1:
    	cmd_name = "COUNT_INFO1";
    	break;
    case CNTR_COUNT_INFO_RESPONSE2:
    	cmd_name = "COUNT_INFO2";
    	break;
    case CNTR_ONLINE_CHECK_RESPONSE:
    	cmd_name = "ONLINE_CHECK";
    	break;
    case CNTR_NOTESERIAL_REPORT_RESPONSE:
    	cmd_name = "SERIAL_REPORT";
    	break;
    case CNTR_STORE_NOTE_CMD:
    	cmd_name = "STORE_START";
    	switch (status) {
    	case CNTR_STATUS_SUCCEED:
    		status_name = tr("Success");
    		break;
    	case CNTR_STATUS_COLLECT_EMPTY:
    		status_name = tr("No Notes in Collection");
			emit store_exception(cmd, MSG_nc_collect_empty, status_name);
    		break;
    	case CNTR_STATUS_REJECT_NOT_EMPTY:
    		status_name = tr("Notes in Reject");
			emit store_exception(cmd, MSG_nc_reject_not_empty, status_name);
    		break;
    	case CNTR_STATUS_ENTRY_NOT_EMPTY:
    		status_name = tr("Notes in Entry");
			emit store_exception(cmd, MSG_nc_entry_not_empty, status_name);
    		break;
    	default:
    		status_name = tr("Unknown Error");
			emit store_exception(cmd, status, status_name);
    		break;
    	}
    	break;
    	break;
    case CNTR_COMPLETE_STORE_NOTE_CMD:
    	cmd_name = "STORE_COMPLETE";
    	switch (status) {
    	case CNTR_STATUS_SUCCEED:
    		status_name = tr("Success");
    		break;
    	case CNTR_STATUS_STORE_FAIL:
    		status_name = tr("Store Faild");
    		emit store_exception(cmd, status, status_name);
    		break;
    	default:
    		status_name = tr("Unknown Error");
    		emit store_exception(cmd, MSG_nc_store_failed, status_name);
    		break;
    	}
    	break;
	case CNTR_FAULT_REPORT_CMD_RESPONSE:
    	cmd_name = "FAULT_REPORT";
    	break;
	default:
		cmd_name = QString("UNKNOWN_CMD %1").arg(cmd, 2, 16, QLatin1Char('0'));
		break;
	}
}

NCController::NCController(QObject *parent)
{
    QSettings settings(JSONSettings::GetSettingFilePath(), JSONSettings::GetFormat(), QCoreApplication::instance());
    NCController_Worker::SerialConfig param;

    param.port = settings.value(JSON_NOTECOUNTER_DEV).toString();
    param.baud = settings.value(JSON_NOTECOUNTER_BAUD).toUInt();
    param.data_bits = (QSerialPort::DataBits) settings.value(JSON_NOTECOUNTER_DATABITS).toUInt();
    param.stop_bits = (QSerialPort::StopBits) settings.value(JSON_NOTECOUNTER_STOPBITS).toUInt();
    param.parity = (QSerialPort::Parity) settings.value(JSON_NOTECOUNTER_PARITY).toUInt();

	m_coordinator = qobject_cast<Coordinator *>(parent);

	m_worker = new NCController_Worker();
	m_worker->setSerialParam(param);
	m_worker->moveToThread(&m_worker_thread);

	connect(&m_worker_thread, &QThread::finished, m_worker, &QObject::deleteLater);
    connect(this, &NCController::start, m_worker, &NCController_Worker::start_work);
    connect(this, &NCController::stop, m_worker, &NCController_Worker::stop_work);

    m_worker_thread.setObjectName("NoteCounter Thread");
	m_worker_thread.start();
}

NCController::~NCController()
{
	m_worker_thread.quit();
	m_worker_thread.wait();
}

